package com.example.androidaudiovideodemo

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.*
import android.hardware.camera2.*
import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import android.media.ImageReader
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
import android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
import android.util.Log
import android.util.Size
import android.view.Surface
import android.view.TextureView
import androidx.core.app.ActivityCompat
import com.blankj.utilcode.util.SDCardUtils
import com.blankj.utilcode.util.ToastUtils
import com.example.androidaudiovideodemo.utils.AvcEncodeUtil
import java.io.File
import java.lang.Exception
import java.util.*
import java.util.concurrent.Executor
import kotlin.Comparator
import kotlin.math.sign

/**
 *
 * author: renbing
 *
 * date: 2022/3/16
 *
 * des:
 */
open class Camera2Preview constructor(context: Context): TextureView(context){

    private val TAG = "Camera2Preview"

    private var mBackgroundHandler: Handler? = null
    private var mBackgroundThread: HandlerThread? = null
    private var mCameraManager: CameraManager? = null
    private var mCameraDevice: CameraDevice? = null
    protected var mCameraCaptureSession: CameraCaptureSession? = null
    protected var mPreviewRequestBuilder: CaptureRequest.Builder? = null
    private var mImageReader: ImageReader? = null
    private var mPreviewSize: Size? = null
    private val CAMERA_FONT = "0"
    private val CAMERA_BACK = "1"
    private var mCameraId: String = ""

    private val STATE_PREVIEW = 0
    private val STATE_RECORD = 1
    private var mState = STATE_PREVIEW
    private val mFrameRate = 30

    private val stateCallback = object : CameraDevice.StateCallback(){
        override fun onOpened(camera: CameraDevice) {
            mCameraDevice = camera
            createCameraPreview()
        }

        override fun onDisconnected(camera: CameraDevice) {
            mCameraDevice?.close()
        }

        override fun onError(camera: CameraDevice, error: Int) {
            mCameraDevice?.close()
            mCameraDevice = null
        }

    }

    private val textureListener = object : SurfaceTextureListener{

        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
            //启动摄像头
            setupCamera()
        }

        override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {

        }

        override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
            return false
        }

        override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {

        }

    }

    init {
        keepScreenOn = true
        getDefaultCameraId()
    }

    fun onResume(){
        startBackgroundThread()
        if (isAvailable){
            setupCamera()
        }else{
            surfaceTextureListener = textureListener
        }
    }

    fun onPause(){
        AvcEncodeUtil.stopThread()
        closeCamera()
        stopBackgroundThread()
    }

    private fun startBackgroundThread(){
        mBackgroundThread = HandlerThread("Camera Thread")
        mBackgroundThread?.start()
        mBackgroundHandler = Handler(mBackgroundThread?.looper!!)
    }

    private fun closeCamera(){
        closePreviewSession()

        mCameraDevice?.close()
        mCameraDevice = null

        mImageReader?.close()
        mImageReader = null
    }

    private fun stopBackgroundThread(){
        mBackgroundThread?.quitSafely()
        try {
            mBackgroundThread?.join()
            mBackgroundThread = null
            mBackgroundHandler = null
        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    private fun closePreviewSession(){
        mCameraCaptureSession?.close()
        mCameraCaptureSession = null
    }

    private fun getDefaultCameraId(){
        mCameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        try {
            val cameraList = mCameraManager?.cameraIdList
            cameraList?.forEach {
                if (it == CAMERA_FONT){
                    mCameraId = it
                    return@forEach
                }else if (it == CAMERA_BACK){
                    mCameraId = it
                    return@forEach
                }
            }
        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    private fun setupCamera(){
        Log.e(TAG,"启动摄像头")
        if (mCameraManager == null){
            Log.e(TAG,"尚未初始化CameraManager")
            return
        }
        try {
            //获取相机特征对象
            val characteristics = mCameraManager?.getCameraCharacteristics(mCameraId)
            //获取相机输出流配置
            val map = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
            mPreviewSize = getPreviewSize(map?.getOutputSizes(SurfaceTexture::class.java),width,height)
            transformImage(width,height)
            if (ActivityCompat.checkSelfPermission(context,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) return

            setupImageReader()
            mCameraManager?.openCamera(mCameraId,stateCallback,null)
        }catch (e: CameraAccessException){
            e.printStackTrace()
        }
    }

    private fun createCameraPreview(){
        try {
            Log.e(TAG,"createCameraPreview")
            val texture = surfaceTexture ?: return
            texture.setDefaultBufferSize(mPreviewSize?.width?:200,mPreviewSize?.height?:200)
            val surface = Surface(texture)
            mPreviewRequestBuilder = mCameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            mPreviewRequestBuilder?.addTarget(surface)
            mPreviewRequestBuilder?.addTarget(mImageReader?.surface!!)

            mCameraDevice?.createCaptureSession(arrayListOf(surface,mImageReader?.surface!!),object : CameraCaptureSession.StateCallback(){
                override fun onConfigured(session: CameraCaptureSession) {
                    if (mCameraDevice == null) return
                    mCameraCaptureSession = session
                    updatePreview()
                }

                override fun onConfigureFailed(session: CameraCaptureSession) {
                    ToastUtils.showShort("Configuration change111")
                }

            },null)

        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    private fun updatePreview(){
        mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_MODE,CameraMetadata.CONTROL_MODE_AUTO)
        try {
            mCameraCaptureSession?.setRepeatingRequest(mPreviewRequestBuilder?.build()!!,null,mBackgroundHandler)
        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    private fun setupImageReader(){
        mImageReader = ImageReader.newInstance(mPreviewSize?.width?:200,mPreviewSize?.height?:200,ImageFormat.YV12,1)
        mImageReader?.setOnImageAvailableListener(ImageReader.OnImageAvailableListener {
            val img = it.acquireNextImage()
            when(mState){
                STATE_PREVIEW ->{
                    AvcEncodeUtil.stopThread()
                    ToastUtils.showShort("停止录制视频成功")
                }
                STATE_RECORD ->{
                    val planes = img.planes
                    var dataYuv: ByteArray? = null
                    if (planes.size >= 3){
                        val bufferY = planes[0].buffer
                        val bufferU = planes[1].buffer
                        val bufferV = planes[2].buffer
                        val lengthY = bufferY.remaining()
                        val lengthU = bufferU.remaining()
                        val lengthV = bufferV.remaining()
                        dataYuv = ByteArray(lengthY+lengthU+lengthV)
                        bufferY.get(dataYuv,0,lengthY)
                        bufferU.get(dataYuv,lengthY,lengthU)
                        bufferV.get(dataYuv,lengthY+lengthU,lengthV)
                    }
                    AvcEncodeUtil.initData(mPreviewSize?.width?:200,mPreviewSize?.height?:200,mFrameRate,getOutputFile(MEDIA_TYPE_VIDEO),false)
                    AvcEncodeUtil.startThread()
                    ToastUtils.showShort("开始录制视频")
                    AvcEncodeUtil.putYuvData(dataYuv)
                }
            }
            img.close()
        },mBackgroundHandler)
    }

    private fun getOutputFile(mediaType: Int): File {
        val sdPath = SDCardUtils.getSDCardPathByEnvironment()
        val rootPath = "$sdPath/${context.packageName}/mediacodec"
        val rootFile = File(rootPath)
        if (!rootFile.exists()){
            rootFile.mkdirs()
        }
        val dir = File(rootFile,if (mediaType == MEDIA_TYPE_IMAGE) "image" else "video")
        if (!dir.exists()){
            dir.mkdirs()
        }
        val file = File(dir,if (mediaType == MEDIA_TYPE_IMAGE) "IMAGE-${System.currentTimeMillis()}.jpg" else "VIDEO-${System.currentTimeMillis()}.h264")
        if (!file.exists()){
            file.createNewFile()
        }
        return file
    }

    private fun getPreviewSize(mapSizes: Array<Size>?,width: Int,height: Int): Size?{
        val sizes = arrayListOf<Size>()
        mapSizes?.forEach {
            if (width > height){
                if (it.width > width && it.height > height){
                    sizes.add(it)
                }
            }else{
                if (it.width > height && it.height > width){
                    sizes.add(it)
                }
            }
        }
        if (sizes.size > 0){
            return Collections.min(sizes, Comparator<Size> { o1, o2 ->
                return@Comparator sign((o1.width*o1.height - o2.width*o2.height).toDouble()).toInt()
            })
        }
        return mapSizes?.get(0)
    }

    private fun transformImage(width: Int,height: Int){
        val matrix = Matrix()
        val rotation = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) context.display?.rotation else (context as Activity).windowManager.defaultDisplay.rotation
        val textureRectF = RectF(0f,0f,width.toFloat(),height.toFloat())
        val previewRectF = RectF(0f,0f,mPreviewSize?.width?.toFloat()?:width.toFloat(),mPreviewSize?.height?.toFloat()?:height.toFloat())
        val centerX = textureRectF.centerX()
        val centerY = textureRectF.centerY()
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270){
            previewRectF.offset(centerX - previewRectF.centerX(),centerY - previewRectF.centerY())
            matrix.setRectToRect(textureRectF,previewRectF,Matrix.ScaleToFit.FILL)
            val scale = Math.max(width/mPreviewSize?.width!!,height/mPreviewSize?.height!!).toFloat()
            matrix.postScale(scale,scale,centerX,centerY)
            matrix.postRotate(90f*(rotation-2),centerX,centerY)
        }
        setTransform(matrix)
    }

    fun toggleVideo(): Boolean{
        mState = if (mState == STATE_PREVIEW) STATE_RECORD else STATE_PREVIEW
        return mState == STATE_RECORD
    }
}